package com.yanqu.road.server.manger;

import com.google.protobuf.Descriptors;
import com.google.protobuf.GeneratedMessage;
import com.yanqu.road.server.TempMgr;
import com.yanqu.road.server.command.Cmd;
import com.yanqu.road.server.command.CmdApi;
import com.yanqu.road.server.command.CommandControl;
import com.yanqu.road.server.command.api.CmdMetaInfo;
import com.yanqu.road.server.command.api.CmdMsgInfo;
import com.yanqu.road.server.command.api.CmdParamMetaInfo;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * wmm
 * 2023/2/5
 */
public class CmdMetaInfoMgr extends TempMgr {

    private static final Logger logger = LogManager.getLogger(CmdMetaInfoMgr.class.getName());

    private static Map<Integer, CmdMetaInfo> metaInfoMap;

    @Override
    public boolean init() throws Exception {
        return reload();
    }

    @Override
    public boolean reloadConfig() throws Exception {
        return true;
    }

    @Override
    public boolean reloadData() throws Exception {
        Map<Integer, Class<?>> commandMap = CommandControl.getCmdPool();
        Map<Integer, CmdMetaInfo> metaInfoMap = new ConcurrentHashMap<>();
        for (Map.Entry<Integer, Class<?>> entry : commandMap.entrySet()) {
            int code = entry.getKey();
            Cmd cmd = entry.getValue().getAnnotation(Cmd.class);
            CmdApi cmdApi = entry.getValue().getAnnotation(CmdApi.class);
            if(cmd != null && cmdApi != null){
                metaInfoMap.put(code, parseCmdMetaInfo(code, cmd, cmdApi));
            }
        }
        this.metaInfoMap = metaInfoMap;
        return true;
    }

    @Override
    public boolean stop() {
        return true;
    }

    private CmdMetaInfo parseCmdMetaInfo(int code, Cmd cmd, CmdApi cmdApi) {
        CmdMetaInfo metaInfo = new CmdMetaInfo();
        metaInfo.setCode(code);
        metaInfo.setName(cmd.desc());

        CmdMsgInfo reqMsg = new CmdMsgInfo();
        List<CmdParamMetaInfo> paramMetaInfoList = getCmdParamMetaInfo(code, cmdApi.reqMsg(), reqMsg);
        metaInfo.setReqMsg(reqMsg);
        metaInfo.setReqParamList(paramMetaInfoList);

        CmdMsgInfo respMsg = new CmdMsgInfo();
        paramMetaInfoList = getCmdParamMetaInfo(code, cmdApi.respMsg(), respMsg);
        metaInfo.setRespMsg(respMsg);
        metaInfo.setRespParamList(paramMetaInfoList);
        return metaInfo;
    }

    private List<CmdParamMetaInfo> getCmdParamMetaInfo(int code, Class<? extends GeneratedMessage> msgCls, CmdMsgInfo msgInfo) {
        try {
            Method method = msgCls.getMethod("getDefaultInstance");
            GeneratedMessage message = (GeneratedMessage)method.invoke(null);
            Descriptors.Descriptor descriptor = message.getDescriptorForType();

            msgInfo.setOutClassName(descriptor.getFile().getOptions().getJavaOuterClassname());
            msgInfo.setFullName(descriptor.getFile().getName());
            msgInfo.setPackageName(msgCls.getPackage().getName());
            String msgName = descriptor.getFullName();
            msgInfo.setMsgName(msgName);

            List<CmdParamMetaInfo> paramMetaInfoList = new ArrayList<>();
            for (Descriptors.FieldDescriptor field : descriptor.getFields()) {
                CmdParamMetaInfo param = getCmdParamMetaInfo(msgCls, descriptor, field);
                paramMetaInfoList.add(param);
                //getLogger().info("cmd api code : {}, msg : {}, param {}", code, msgInfo.getMsgName(), field.getName());
            }
            return paramMetaInfoList;
        } catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
            getLogger().error(e.getMessage(), e);
        }
        return new ArrayList<>();
    }

    private CmdParamMetaInfo getCmdParamMetaInfo(Class<? extends GeneratedMessage> reqMsg, Descriptors.Descriptor descriptor, Descriptors.FieldDescriptor field) {
        CmdParamMetaInfo param = new CmdParamMetaInfo();
        param.setIndex(field.getIndex());
        param.setFieldName(field.getName());
        param.setType(field.getType().name().toLowerCase());
        if(field.isRequired()){
            param.setRequired(true);
        }
        if(field.isOptional()){
            param.setOptional(true);
        }
        if(field.isRepeated()){
            param.setRepeated(true);
        }
        return param;
    }



    public static Map<Integer, CmdMetaInfo> getMetaInfoMap() {
        return metaInfoMap;
    }
}
